package com.cty.aio;

import org.apache.log4j.Logger;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.Scanner;

/**
 * @Auther: cty
 * @Date: 2020/4/18 19:35
 * @Description:
 * @version: 1.0
 */
public class AIOServerHandler implements CompletionHandler<AsynchronousSocketChannel, AIOServer> {
    Logger log = Logger.getLogger(AIOServerHandler.class);
    /**
     * 业务处理逻辑——当请求到来并监听成功后做什么
     * @param result 和客户端建立连接的通道，在BIO、NIO和AIO中，一旦连接建立两端就是平等的
     *               result中有通道中所有的相关数据，如OS准备好的读数据缓存，或等待写出的数据缓存
     * @param attachment
     */
    @Override
    public void completed(AsynchronousSocketChannel result, AIOServer attachment) {
        log.debug("now in accept completed");
        // 开启对下一次客户端请求的监听
        attachment.getServerChannel().accept(attachment, this);
        // 首先执行读操作
        doRead(result);
    }

    /**
     * 异常处理逻辑——当服务端代码出现异常时做什么
     * @param exc
     * @param attachment
     */
    @Override
    public void failed(Throwable exc, AIOServer attachment) {
        exc.printStackTrace();
    }

    /**
     * 执行读数据操作
     * @param channel 和客户端建立连接的通道
     */
    private void doRead(final AsynchronousSocketChannel channel){
        try {
            System.out.println("Do reading from "+channel.getRemoteAddress()+" ...");
        } catch (IOException e) {
            System.out.println("获取远程地址失败！");
//            e.printStackTrace();
        }
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        /**
         * 属类：AsynchronousSocketChannel
         * 方法：public final <A> void read(ByteBuffer dst,
         *                            A attachment,
         *                            CompletionHandler<Integer,? super A> handler)
         * 参数：dst-处理客户端传递数据的中转缓存，可以不使用
         *      attachment-附件，处理客户端传递数据的对象，通常使用Buffer
         *      handler-完成处理器
         * 返回：读取成功，外部无返回，内部会将存放读取数据的缓存和读取数据的字节数传递给完成处理器
         * 作用：使用通道组线程池里的一个新线程去监听读取，读取成功会将存放读取数据的缓存和读取数据的字节数传递
         *      给完成处理器，然后执行完成处理器的completed()方法；读取失败执行处理器的failed()方法。
         * 说明：
         *      监听读取和完成处理器的执行一般在同一个线程。
         *      （假设一次性没读完，又读了一次）如果到了读数据流末尾，传递到完成处理器的缓存为null，字节数为-1。
         */
        channel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
            /**
             * 业务逻辑——读取客户端传输数据
             * @param result
             * @param attachment  在completed方法执行的时候，OS已经将客户端请求的数据写入到Buffer中了
             *                    但是Buffer未复位（flip），使用前一定要复位！
             */
            @Override
            public void completed(Integer result, ByteBuffer attachment) {
                log.debug("now in read completed");
                System.out.println("now in doRead.completed()");
                try {
                    // 缓存复位
                    attachment.flip();
                    // 准备字节数组
                    byte[] bytes = new byte[attachment.remaining()];
                    // 将缓存数据读取到字节数组
                    attachment.get(bytes);
                    // 控制台打印来自客户端的请求数据
                    System.out.println("Read from "+channel.getRemoteAddress()+" client: "+new String(bytes, "UTF-8"));
                    // 服务端向客户端响应数据
                    dowrite(channel);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void failed(Throwable exc, ByteBuffer attachment) {
                exc.printStackTrace();
            }
        });
    }

    /**
     *
     * @param channel  和客户端建立连接的通道
     */
    private void dowrite(final AsynchronousSocketChannel channel){
        try {
            System.out.println("Do writing to "+channel.getRemoteAddress()+" ...");

            ByteBuffer buffer =  ByteBuffer.allocate(1024);
            System.out.println("Enter massage send to "+channel.getRemoteAddress()+" client > ");
            Scanner scanner = new Scanner(System.in);
            String message = scanner.nextLine();
            buffer.put(message.getBytes("UTF-8"));
            buffer.flip();
            /**
             * 属类：AsynchronousSocketChannel
             * 方法：public final <A> void write(ByteBuffer src,
             *                             A attachment,
             *                             CompletionHandler<Integer,? super A> handler)
             * 参数：dst-处理客户端传递数据的中转缓存，可以不使用
             *      attachment-附件，处理客户端传递数据的对象，通常使用Buffer
             *      handler-处理器
             * 返回：写出成功，外部无返回，内部会将存放写出数据的缓存和写出字节数传递给完成处理器
             * 作用：使用通道组线程池里的一个新线程去监听写出，写出成功会将存放写出数据的缓存和写出数据的字节数传递给
             *      处理器，然后执行完成处理器的completed()方法；写出失败执行处理器的failed()方法。
             * 说明：
             *      监听写出和完成处理器的执行一般在同一个线程。
             *
             *      ！！！此处使用的是：Future<Integer> write(ByteBuffer src)，详细介绍见AIOClient
             */
            channel.write(buffer);  // 非阻塞方法
            // channel.write(buffer).get();  // get服务端线程阻塞，等待操作完成
            // 再次等待读取客户端的信息
            doRead(channel);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
